/**
 * @file factory.jsx
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */
import * as React from 'react';
import {connect} from 'react-redux';
import * as assign from 'object-assign';
import * as find from 'lodash/find';
import {createScopedReducer} from './util';
import * as Ajv from 'ajv/dist/ajv.min';
import * as memory from 'lodash/memoize';
import * as isPlainObject from 'lodash/isPlainObject';
import * as cloneDeep from 'lodash/cloneDeep';
import {filter, dataMapping} from './util';
const mapping = {};
const renderers = [];
const reducers = {};
const thirdRenderers = [];
const schemas = [];

const composeRender = ComposedComponent => class Renderer extends React.PureComponent {
  static contextTypes = {
    scoped: React.PropTypes.object
  };

  constructor(props) {
    super(props);

    if (props.validateSchema) {
      this.state = {}
    }

    this.state = {
      schemaErrors: props.validateSchema ? this.validate(props) : null
    }
  }

  componentWillReceiveProps(nextProps) {
    this.state = {
      schemaErrors: nextProps.validateSchema ? this.validate(nextProps) : null
    }
  }

  validate(props) {
    const validateSchema = this.props.validateSchema;
    const validator = getSchemaValidator(schemas);

    if (!validator.validate(validateSchema, props.amisConfig)) {
      locale_zh(validator.errors);
      return validator.errors ? validator.errors.concat() : null;
    }
  }

  render() {
    const {
      name,
      $index,
      $path,
      $scopedPrefix,
      data,
      locals,
      rendererName
    } = this.props;
    const scoped = this.context.scoped;

    if (this.state.schemaErrors) {
      return (
        <div className="alert alert-danger text-left">
          <p>{rendererName} 组件格式配置错误：</p>
          <ul className="m-t-sm">
            {this.state.schemaErrors.map((error, key) => (
              <li key={key}>{error.dataPath ? `${error.dataPath} ` : ''}{error.message}</li>
            ))}
          </ul>
        </div>
      );
    }

    const scopedKey = `${scoped && scoped.getData('pageId') || $scopedPrefix ||  'global'}-${scoped && scoped.getData('id', '') || '0'}-` + (name && filter(name, data, locals) || `${$path.replace(/\//g, '-')}${$index ? `-${$index}` : ''}`);
    return <ComposedComponent {...this.props} scopedKey={scopedKey} amisConfig={null} />;
  }
};

// Renderer 注册
export const Renderer = (config, ...args) => Component => {

  if (!config.name || !config.test) {
    throw new Error('模型注册格式错误');
  } else if (mapping[config.name]) {
    throw new Error(`${config.name} 已经注册了, 不能重复注册`);
  }

  const rendererName = config.name;

  if (args.length) {
    let mapper = args.shift();

    if (!mapper) {
      mapper = (state, props) => ({
        state: state.renderer[rendererName][props.scopedKey] || state.renderer[rendererName]['unScoped'] || {}
      })
    }

    args.unshift(mapper);
    Component = composeRender(connect(...args)(Component));
  } else if (config.validateSchema) {
    Component = composeRender(Component);
  }

  config.Component = Component;

  // 用 renderer 的 name 隔离 reducer.
  config.reducer && (reducers[rendererName] = createScopedReducer(config.reducer));
  config.schema && schemas.push(config.schema);

  mapping[rendererName] = config;
  renderers.push(config);

  return Component;
};

/**
 * 暴露给用户扩展. 内部请直接使用 Renderer 来注册
 *
 * todo 补充更多的使用说明.
 *
 * @type {function(): function()}
 */
export const CustomRenderer = (config, ...args) => Component => {
  if (!config.name || !config.test) {
    throw new Error('模型注册格式错误');
  }

  config.name = `third-` + config.name;

  const rendererName = config.name;

  if (args.length) {
    let mapper = args.shift();

    if (!mapper) {
      mapper = (state, props) => ({
        state: state.renderer[rendererName][props.scopedKey] || state.renderer[rendererName]['unScoped'] || {}
      })
    }

    args.unshift(mapper);
    Component = composeRender(connect(...args)(Component));
  }

  config.Component = Component;

  // 用 renderer 的 name 隔离 reducer.
  config.reducer && (reducers[rendererName] = createScopedReducer(config.reducer));
  config.schema && schemas.push(config.schema);

  thirdRenderers.push(config);

  return Component;
};

export const getSchemas = () => cloneDeep(schemas);
const getSchemaValidator = memory(schemas => new Ajv({
  allErrors: true,
  schemas: cloneDeep(schemas)
}));

export const render = (path, config = {}, props = {}, $key = '') => {
  if (config.children && typeof config.children === 'function') {
    return config.children(props, path);
  }

  const renderer = getRenderer(path, config);
  if (!renderer) {
    // throw new Error(`调用错误, 找不对对应的渲染模型. \npath: ${path}\ndata: ${JSON.stringify(config, null, 2)}`);
    return (
      <div className="alert alert-danger" key={props.key}>
        <p>调用错误, 找不对对应的渲染模型. </p>
        <p>path: {path}</p>
        <pre><code>{JSON.stringify(config, null, 2)}</code></pre>
      </div>
    )
  }

  // console.log(renderer.name);

  const Component = renderer.Component;

  if (isPlainObject(config.data) && props.data) {
    props.data = dataMapping(config.data, {
      ...props.locals,
      ...props.data
    });
  }

  return (
    <Component
      {...renderer.filter ? renderer.filter(config, path) : config}
      {...props}
      $path={path}
      $key={$key}
      rendererName={renderer.name}
      amisConfig={config}
      validateSchema={typeof renderer.validateSchema === 'string' ? renderer.validateSchema : (renderer.validateSchema && renderer.schema.id) }
    />
  );
};

export const renderChildren = (pathPrefix, schema, props = {}, schemaMapper = null, propsMapper = null, $key) => {
  let isSchemaArray = true;

  if (!Array.isArray(schema)) {
    isSchemaArray = false;
    schema = [schema];
  }

  return schema.map((child, key) => {
    if (typeof child === 'string') {
      child = {
        type: 'tpl',
        tpl: child
      };
    }

    props = assign({}, props, {
      $index: key,
      key: key
    });

    schemaMapper && (child = schemaMapper(child));
    propsMapper && (props = propsMapper(props, child));

    return child ? render(`${pathPrefix}/${child.type}`, child, props, isSchemaArray ? `${$key}.${key}` : $key) : null;
  });
};

export const getReducers = () => reducers;

const caches = {};
function getRenderer(path, config) {
  let resolver = item => {
    let matched = false;

    if (typeof item.test === 'function') {
      matched = item.test(path, config);
    } else if (item.test instanceof RegExp) {
      matched = item.test.test(path);
    }

    return matched;
  };

  // const key = path + (config.type ? `/${config.type}` : '');
  // if (caches[key]) {
  //   return caches[key];
  // }

  const resolved = find(renderers, resolver) || find(thirdRenderers, resolver);

  // if (resolved) {
  //   caches[key] = resolved;
  // }

  return resolved;
}

function locale_zh(errors) {
  if (!(errors && errors.length)) return;
  for (var i = 0; i < errors.length; i++) {
    var e = errors[i];
    var out;
    switch (e.keyword) {
      case '$ref':
        out = '无法找到引用' + (e.params.ref);
        break;
      case 'additionalItems':
        out = '';
        var n = e.params.limit;
        out += '不允许超过' + (n) + '个元素';
        break;
      case 'additionalProperties':
        out = '不允许有额外的属性：' + e.params.additionalProperty;
        break;
      case 'anyOf':
        out = '数据应为 anyOf 所指定的其中一个';
        break;
      case 'constant':
        out = '应当等于常量';
        break;
      case 'custom':
        out = '应当通过 "' + (e.keyword) + ' 关键词校验"';
        break;
      case 'dependencies':
        out = '';
        var n = e.params.depsCount;
        out += '应当拥有属性' + (e.params.property) + '的依赖属性' + (e.params.deps);
        break;
      case 'enum':
        out = '应当是预设定的枚举值之一';
        break;
      case 'format':
        out = '应当匹配格式 "' + (e.params.format) + '"';
        break;
      case 'formatExclusiveMaximum':
        out = 'formatExclusiveMaximum 应当是布尔值';
        break;
      case 'formatExclusiveMinimum':
        out = 'formatExclusiveMinimum 应当是布尔值';
        break;
      case 'formatMaximum':
        out = '';
        var cond = e.params.comparison + " " + e.params.limit;
        out += '应当是 ' + (cond);
        break;
      case 'formatMinimum':
        out = '';
        var cond = e.params.comparison + " " + e.params.limit;
        out += '应当是 ' + (cond);
        break;
      case 'maximum':
        out = '';
        var cond = e.params.comparison + " " + e.params.limit;
        out += '应当为 ' + (cond);
        break;
      case 'maxItems':
        out = '';
        var n = e.params.limit;
        out += '不应多于 ' + (n) + ' 个项';
        break;
      case 'maxLength':
        out = '';
        var n = e.params.limit;
        out += '不应多于 ' + (n) + ' 个字符';
        break;
      case 'maxProperties':
        out = '';
        var n = e.params.limit;
        out += '不应有多于 ' + (n) + ' 个属性';
        break;
      case 'minimum':
        out = '';
        var cond = e.params.comparison + " " + e.params.limit;
        out += '应当为 ' + (cond);
        break;
      case 'minItems':
        out = '';
        var n = e.params.limit;
        out += '不应少于 ' + (n) + ' 个项';
        break;
      case 'minLength':
        out = '';
        var n = e.params.limit;
        out += '不应少于 ' + (n) + ' 个字符';
        break;
      case 'minProperties':
        out = '';
        var n = e.params.limit;
        out += '不应有少于 ' + (n) + ' 个属性';
        break;
      case 'multipleOf':
        out = '应当是 ' + (e.params.multipleOf) + ' 的整数倍';
        break;
      case 'not':
        out = '不应当匹配 "not" schema';
        break;
      case 'oneOf':
        out = '只能匹配一个 "oneOf" 中的 schema';
        break;
      case 'pattern':
        out = '应当匹配模式 "' + (e.params.pattern) + '"';
        break;
      case 'patternGroups':
        out = '';
        var n = e.params.limit;
        out += '应当有 ' + (n) + ' 个 ' + (e.params.reason) + ' 属性满足模式 ' + (e.params.pattern);
        break;
      case 'patternRequired':
        out = '应当有属性匹配模式 ' + (e.params.missingPattern);
        break;
      case 'required':
        out = '缺少必填属性 ' + (e.params.missingProperty);
        break;
      case 'switch':
        out = '由于 ' + (e.params.caseIndex) + ' 失败，未通过 "switch" 校验, ';
        break;
      case 'type':
        out = '应当是 ' + (e.params.type) + ' 类型';
        break;
      case 'uniqueItems':
        out = '不应当含有重复项 (第 ' + (e.params.j) + ' 项与第 ' + (e.params.i) + ' 项是重复的)';
        break;
      default:
        continue;
    }
    e.message = out;
  }

}
